<template>
  <div class="designer-container">
    <!-- 左侧工具栏 -->
    <div
      class="designer-sidebar"
      :class="{ 'sidebar-collapsed': !sidebarExpanded }"
    >
      <el-tabs
        v-model="activeTab"
        class="designer-tabs"
        v-show="sidebarExpanded"
      >
        <el-tab-pane label="基础节点" name="base">
          <div class="sidebar-title">基础节点</div>
          <div class="sidebar-list">
            <div
              v-for="item in filteredNodeConfigList"
              :key="item.type"
              class="sidebar-item"
              draggable="true"
              @dragstart="onDragStart(item.type)"
            >
              <svg-icon :icon-class="item.cfg.icon" class="sidebar-icon" />
              <span v-show="sidebarExpanded">{{ item.cfg.name.replace(/节点$/, '') }}</span>
            </div>
          </div>
        </el-tab-pane>
        <el-tab-pane label="业务工具" name="business">
          <div class="sidebar-title">业务工具</div>
          <div class="sidebar-list">
            <!-- 可根据业务需求扩展业务工具节点 -->
            <div class="sidebar-item">
              <svg-icon icon-class="tool" class="sidebar-icon" /><span
                v-show="sidebarExpanded"
                >业务工具</span
              >
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
      <div class="sidebar-expand-btn" @click="toggleSidebar">
        {{ sidebarExpanded ? "«" : "»" }}
      </div>
    </div>
    <!-- 右侧主画布 -->
    <div class="designer-main">
      <div class="designer-header">
        <span class="designer-title">{{ workflowName || "工作流设计器" }}</span>
        <div style="flex:1"></div>
        <div class="designer-actions">
          <el-button
            style="margin-right: 8px"
            @click="$router.push({ path: '/flow/workflow' })"
            >返回</el-button
          >
          <el-button
            type="primary"
            style="margin-right: 8px"
            @click="handleSave"
            :disabled="!workflowId || workflowId === 'undefined'"
            >保存 (Ctrl + s)</el-button
          >
          <el-button
            type="danger"
            style="margin-right: 8px"
            @click="handleDeleteAllNodes"
            >全部删除</el-button
          >
        </div>
      </div>
      <div
        class="designer-canvas"
        :style="{ transform: `scale(${canvasScale})`, transformOrigin: '0 0' }"
        @wheel="handleWheel"
        @dragover.prevent
        @drop="onDrop"
      >
        <!-- 画布区域，支持拖拽释放并渲染节点 -->
        <div
          v-for="(node, i) in nodes"
          :key="node.id"
          class="canvas-node"
          :id="'node-' + node.id"
          :style="{ left: node.x + 'px', top: node.y + 'px' }"
          @mousedown="onNodeMouseDown($event, i)"
        >
          <!-- 锚点渲染：开始节点只右侧，结束节点只左侧，其它节点左右各一个 -->
          <div
            v-if="node.type === 'start'"
            class="anchor-point"
            :id="'anchor-out-' + node.id"
            style="right: 0; left: auto"
          ></div>
          <div
            v-else-if="node.type === 'end'"
            class="anchor-point"
            :id="'anchor-in-' + node.id"
            style="
              left: 0;
              top: 0;
              right: auto;
              transform: translate(-50%, -50%);
            "
          ></div>
          <template v-else>
            <div
              class="anchor-point"
              :id="'anchor-in-' + node.id"
              style="
                left: 0;
                top: 0;
                right: auto;
                transform: translate(-50%, -50%);
              "
            ></div>
            <div
              class="anchor-point"
              :id="'anchor-out-' + node.id"
              style="right: 0; left: auto"
            ></div>
          </template>
          <div class="node-actions">
            <el-button
              icon="el-icon-document-copy"
              size="mini"
              circle
              @click.stop="copyNode(i)"
            />
            <el-button
              icon="el-icon-delete"
              size="mini"
              circle
              @click.stop="deleteNode(i)"
            />
            <el-button
              icon="el-icon-setting"
              size="mini"
              circle
              @click.stop="settingNode(i)"
            />
          </div>
          <component
            v-if="
              NODE_TYPES[node.type] &&
              $options.components[NODE_TYPES[node.type]]
            "
            :is="NODE_TYPES[node.type]"
            :config="node.config"
            @change="(val) => (node.config = val)"
          />
          <div v-else>{{ node.type }}</div>
          <!-- 悬浮设置卡片 -->
          <div
            v-if="
              settingNodeIndex === i && nodes[i] && nodes[i].name !== undefined
            "
            class="node-setting-card"
            @click.stop
          >
            <div style="margin-bottom: 10px">
              <label>节点名称：</label>
              <el-input
                size="mini"
                v-model="nodes[i].name"
                @input="(val) => handleSettingInput('name', val)"
              />
            </div>
            <div style="margin-bottom: 10px">
              <label>参数描述：</label>
              <el-input
                size="mini"
                type="textarea"
                v-model="nodes[i].desc"
                @input="(val) => handleSettingInput('desc', val)"
              />
            </div>
            <div style="margin-bottom: 6px">
              <el-checkbox
                v-model="nodes[i].async"
                @change="(val) => handleSettingInput('async', val)"
                >异步执行</el-checkbox
              >
            </div>
            <div>
              <el-checkbox
                v-model="nodes[i].loop"
                @change="(val) => handleSettingInput('loop', val)"
                >循环执行</el-checkbox
              >
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- 工具栏底部缩放按钮 -->
    <div class="sidebar-zoom-btns">
      <button class="zoom-btn" @click="zoomIn">+</button>
      <button class="zoom-btn" @click="zoomOut">-</button>
      <button class="zoom-btn" @click="fitCanvas">□</button>
    </div>
    <!-- 连线属性抽屉 -->
    <el-drawer
      title="边属性设置"
      :visible.sync="showLinkDrawer"
      direction="rtl"
      size="30%"
    >
      <div v-if="selectedLinkIndex !== null && links[selectedLinkIndex]">
        <el-input
          v-model="links[selectedLinkIndex].attr.condition"
          placeholder="边条件设置"
        />
        <el-button
          style="margin-top: 16px"
          type="primary"
          @click="showLinkDrawer = false"
          >保存</el-button
        >
      </div>
    </el-drawer>
    <!-- 连线小卡片 -->
    <div
      v-if="linkCard.show && links[linkCard.idx]"
      :style="{ left: linkCard.x + 'px', top: linkCard.y + 'px' }"
      class="link-card"
      @click.stop
    >
      <el-input
        v-model="links[linkCard.idx].attr.condition"
        size="mini"
        placeholder="条件"
        @keyup.native="handleLinkCardInput"
      />
      <div style="display: flex; gap: 8px; justify-content: flex-end">
        <el-button size="mini" @click="deleteLink(linkCard.idx)"
          >删除</el-button
        >
        <el-button size="mini" type="primary" @click="saveLinkCard"
          >保存</el-button
        >
      </div>
    </div>
  </div>
</template>

<script>
import SvgIcon from "@/components/SvgIcon/index.vue";
import * as NodeComponents from "@/components/Node/index.js";
import { NODE_TYPES, NODE_CONFIG } from "@/components/Node/index.js";
import { jsPlumb } from "jsplumb";
import { getWorkflowDesign, saveWorkflowDesign } from "@/api/system/workflow";

export default {
  name: "WorkflowDesign",
  components: {
    SvgIcon,
    ...NodeComponents,
  },
  data() {
    return {
      activeTab: "base",
      sidebarExpanded: true,
      canvasScale: 1,
      showRunDrawer: false,
      nodes: [],
      dragNodeType: null,
      draggingNodeIndex: null,
      dragOffset: { x: 0, y: 0 },
      settingNodeIndex: null,
      isDraggingNode: false,
      ignoreNextClick: false,
      linking: false,
      linkFromIndex: null,
      linkToIndex: null,
      tempLinkPos: { x: 0, y: 0 },
      links: [],
      selectedLinkIndex: null,
      showLinkDrawer: false,
      linkCard: { show: false, x: 0, y: 0, idx: null },
      hoveredNodeIndex: null,
      jsPlumbInstance: null,
      workflowId: null, // 新增：工作流ID
      workflowName: "", // 新增：工作流名称
      isDesignModified: false, // 新增：设计是否已修改
      executionResult: null, // 新增：执行结果
      executionSteps: [], // 新增：执行步骤
      NODE_TYPES,
      NODE_CONFIG,
    };
  },
  async created() {
    this.workflowId = this.$route.params.id || this.$route.query.id;
    if (!this.workflowId || this.workflowId === "undefined") {
      this.$message.error("未获取到有效的工作流ID");
      return;
    }
    await this.loadWorkflowDesign();
  },
  mounted() {
    this._handleClickOutsideSetting = this.handleClickOutsideSetting.bind(this);
    window.addEventListener("click", this._handleClickOutsideSetting);
    window.addEventListener("keydown", this.handleSaveShortcut);
    window.addEventListener("click", this.handleClickOutsideLinkCard);
    this.$nextTick(() => {
      this.initJsPlumb();
      this.addAllJsPlumbAnchors();
      this.renderAllConnections();
    });
  },
  watch: {
    nodes: {
      handler() {
        this.isDesignModified = true;
        this.$nextTick(() => {
          this.addAllJsPlumbAnchors();
          this.renderAllConnections();
        });
      },
      deep: true,
    },
    links: {
      handler() {
        this.isDesignModified = true;
        this.$nextTick(() => {
          this.renderAllConnections();
        });
      },
      deep: true,
    },
  },
  beforeDestroy() {
    window.removeEventListener("click", this._handleClickOutsideSetting);
    window.removeEventListener("keydown", this.handleSaveShortcut);
    window.removeEventListener("click", this.handleClickOutsideLinkCard);
    if (this.jsPlumbInstance) {
      this.jsPlumbInstance.reset();
      if (typeof this.jsPlumbInstance.destroy === "function") {
        this.jsPlumbInstance.destroy();
      }
      this.jsPlumbInstance = null;
    }
  },
  computed: {
    filteredNodeConfigList() {
      const types = [
        'start', 'loop', 'ai', 'knowledge', 'search', 'http', 'code', 'template',
        'extract', 'generate', 'sql', 'plugin', 'end'
      ];
      return Object.entries(this.NODE_CONFIG)
        .filter(([type]) => types.includes(type))
        .map(([type, cfg]) => ({ type, cfg }));
    }
  },
  methods: {
    // 新增：加载工作流设计
    async loadWorkflowDesign() {
      try {
        const response = await getWorkflowDesign(this.workflowId);
        if (response.code === 200) {
          const workflow = response.data;
          this.workflowName = workflow.name;

          // 转换节点数据格式
          if (workflow.nodes) {
            this.nodes = workflow.nodes.map((node) => ({
              id: node.nodeId,
              type: node.type,
              x: node.x,
              y: node.y,
              name: node.name,
              desc: node.desc,
              async: node.async,
              loop: node.loop,
              config: node.config
                ? JSON.parse(node.config)
                : this.getDefaultConfig(node.type),
            }));
          }

          // 转换连线数据格式
          if (workflow.links) {
            this.links = workflow.links.map((link) => ({
              from: this.nodes.findIndex((n) => n.id === link.fromNodeId),
              to: this.nodes.findIndex((n) => n.id === link.toNodeId),
              attr: link.attr
                ? JSON.parse(link.attr)
                : { condition: link.condition || "" },
            }));
          }

          this.isDesignModified = false;
        }
      } catch (error) {
        this.$message.error("加载工作流设计失败: " + error.message);
      }
    },

    // 新增：保存工作流设计
    async handleSave() {
      if (!this.workflowId || this.workflowId === "undefined") {
        this.$message.error("未获取到有效的工作流ID，无法保存");
        return;
      }

      try {
        // 转换数据格式
        const workflowData = {
          id: this.workflowId,
          nodes: this.nodes.map((node) => ({
            nodeId: node.id,
            name: node.name,
            type: node.type,
            desc: node.desc,
            x: node.x,
            y: node.y,
            async: node.async,
            loop: node.loop,
            config: JSON.stringify(node.config),
          })),
          links: this.links.map((link, index) => ({
            linkId: "link_" + index,
            fromNodeId: this.nodes[link.from]?.id,
            toNodeId: this.nodes[link.to]?.id,
            condition: link.attr?.condition || "",
            attr: JSON.stringify(link.attr),
          })),
        };

        const response = await saveWorkflowDesign(workflowData);
        if (response.code === 200) {
          this.$message.success("保存成功");
          this.isDesignModified = false;
        } else {
          this.$message.error("保存失败: " + response.msg);
        }
      } catch (error) {
        this.$message.error("保存失败: " + error.message);
      }
    },

    // 新增：获取节点默认配置
    getDefaultConfig(type) {
      if (this.NODE_CONFIG[type]) {
        // 可根据不同类型返回不同默认配置
        switch (type) {
          case "knowledge":
            return {
              knowledgeBase: "",
              searchType: "semantic",
              topK: 5,
              similarityThreshold: 0.7,
              query: "",
              saveToVariable: false,
              outputVariable: "",
            };
          case "ai":
            return { prompt: "", model: "gpt-3.5-turbo", temperature: 0.7 };
          case "search":
            return { engine: "bing", query: "" };
          case "http":
            return { url: "", method: "GET", headers: {}, body: "" };
          case "code":
            return { code: "", inputVar: "", outputVar: "" };
          case "template":
            return { template: "", variables: [] };
          case "extract":
            return { fileType: "", extractType: "text" };
          case "generate":
            return { fileName: "", content: "" };
          case "sql":
            return { sql: "", db: "" };
          case "plugin":
            return { pluginId: "", params: {} };
          default:
            return {};
        }
      }
      return {};
    },

    // 新增：节点执行模拟函数
    async executeNode(node, input) {
      switch (node.type) {
        case "start":
          return { output: input, status: "success", message: "开始执行" };
        case "end":
          return { output: input, status: "success", message: "流程结束" };
        default:
          return { output: input, status: "success", message: "节点执行完成" };
      }
    },
    // 重写试运行主流程
    async executeWorkflow() {
      if (!this.workflowId || this.workflowId === "undefined") {
        this.$message.error("未获取到有效的工作流ID，无法试运行");
        return;
      }
      // 1. 构建节点id到节点对象的映射
      const nodeMap = {};
      this.nodes.forEach((n, idx) => {
        nodeMap[n.id] = { ...n, idx };
      });
      // 2. 构建邻接表（有向图）
      const adj = {};
      this.nodes.forEach((n) => {
        adj[n.id] = [];
      });
      this.links.forEach((link) => {
        const fromId = this.nodes[link.from]?.id;
        const toId = this.nodes[link.to]?.id;
        if (fromId && toId) adj[fromId].push(toId);
      });
      // 3. 找到开始节点
      const startNode = this.nodes.find((n) => n.type === "start");
      if (!startNode) {
        this.$message.error("未找到开始节点");
        return;
      }
      // 4. 执行流程
      let currentId = startNode.id;
      let input = { testParam: "测试参数" };
      const steps = [];
      const visited = new Set();
      while (currentId && !visited.has(currentId)) {
        visited.add(currentId);
        const node = nodeMap[currentId];
        const result = await this.executeNode(node, input);
        steps.push({
          step: steps.length + 1,
          node: node.name || node.type,
          status: result.status,
          message: result.message,
          output: result.output,
        });
        input = result.output;
        // 找下一个节点（只取第一个分支）
        currentId = adj[currentId][0];
      }
      this.executionResult = input;
      this.executionSteps = steps;
      this.showRunDrawer = true;
    },

    handleSaveShortcut(e) {
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
        e.preventDefault();
        this.handleSave();
      }
    },
    toggleSidebar() {
      this.sidebarExpanded = !this.sidebarExpanded;
    },
    zoomIn() {
      this.canvasScale = Math.min(this.canvasScale + 0.1, 2);
      this.$nextTick(this.centerCanvas);
    },
    zoomOut() {
      this.canvasScale = Math.max(this.canvasScale - 0.1, 0.2);
      this.$nextTick(this.centerCanvas);
    },
    fitCanvas() {
      this.canvasScale = 1;
      this.$nextTick(this.centerCanvas);
    },
    centerCanvas() {
      // 让画布内容居中显示
      const canvas = this.$el.querySelector(".designer-canvas");
      if (canvas) {
        const parent = canvas.parentElement;
        if (parent) {
          parent.scrollLeft =
            (canvas.scrollWidth * this.canvasScale - parent.clientWidth) / 2;
          parent.scrollTop =
            (canvas.scrollHeight * this.canvasScale - parent.clientHeight) / 2;
        }
      }
    },
    handleWheel(e) {
      // 鼠标滚轮缩放画布
      e.preventDefault();
      if (e.deltaY < 0) {
        // 向上滚动，放大
        this.canvasScale = Math.min(this.canvasScale + 0.05, 2);
      } else {
        // 向下滚动，缩小
        this.canvasScale = Math.max(this.canvasScale - 0.05, 0.2);
      }
    },
    // 拖拽开始
    onDragStart(type) {
      this.dragNodeType = type;
    },
    handleClickOutsideLinkCard(e) {
      if (!e.target.closest(".link-card")) {
        this.linkCard.show = false;
      }
    },
    showLinkCard(idx, conn) {
      try {
        // 更严格的防御判断
        if (
          !conn ||
          !Array.isArray(conn.endpoints) ||
          !conn.endpoints[0] ||
          !conn.endpoints[1]
        ) {
          console.error("连线对象或端点未定义", conn);
          this.$message.error("连线数据异常，无法显示属性卡片");
          return;
        }
        // 获取 source/target 节点的 DOM
        const sourceId = conn.endpoints[0].elementId;
        const targetId = conn.endpoints[1].elementId;
        const sourceEl = document.getElementById(sourceId);
        const targetEl = document.getElementById(targetId);
        if (sourceEl && targetEl) {
          const sourceRect = sourceEl.getBoundingClientRect();
          const targetRect = targetEl.getBoundingClientRect();
          // 计算中点
          const midX =
            (sourceRect.left + targetRect.left) / 2 + sourceRect.width / 2;
          const midY =
            (sourceRect.top + targetRect.top) / 2 + sourceRect.height / 2;
          this.linkCard = {
            show: true,
            x: midX,
            y: midY,
            idx,
          };
          // 自动聚焦输入框
          this.$nextTick(() => {
            const input = document.querySelector(".link-card input");
            if (input) input.focus();
          });
        } else {
          console.error("sourceEl或targetEl未找到", sourceEl, targetEl);
          this.$message.error("未找到连线端点元素");
        }
      } catch (err) {
        console.error("showLinkCard异常", err, idx, conn);
        this.$message.error("显示连线属性卡片时发生错误");
      }
    },
    deleteLink(idx) {
      this.links.splice(idx, 1);
      this.linkCard.show = false;
      this.$nextTick(() => {
        this.renderAllConnections(); // 新增：连线删除后重新渲染
      });
    },
    saveLinkCard() {
      this.linkCard.show = false;
    },
    handleLinkCardInput(e) {
      // 回车保存
      if (e.key === "Enter") {
        this.saveLinkCard();
      }
    },
    initJsPlumb() {
      if (this.jsPlumbInstance) return; // 只初始化一次
      this.jsPlumbInstance = jsPlumb.getInstance({
        Container: this.$el.querySelector(".designer-canvas"),
      });
      // 绑定 beforeDrop 事件
      this.jsPlumbInstance.bind("beforeDrop", (info) => {
        const fromIdx = this.nodes.findIndex(
          (n) => "anchor-out-" + n.id === info.sourceId
        );
        const toIdx = this.nodes.findIndex(
          (n) => "anchor-in-" + n.id === info.targetId
        );
        if (
          fromIdx !== -1 &&
          toIdx !== -1 &&
          !this.links.some((l) => l.from === fromIdx && l.to === toIdx)
        ) {
          this.links.push({
            from: fromIdx,
            to: toIdx,
            attr: { condition: "" },
          });
          this.$nextTick(() => this.renderAllConnections());
        }
        return false; // 阻止 jsPlumb 自动加线
      });
    },
    addAllJsPlumbAnchors() {
      if (!this.jsPlumbInstance) return;
      this.nodes.forEach((node) => {
        this.addJsPlumbAnchorsForNode(node);
      });
    },
    addJsPlumbAnchorsForNode(node) {
      const nodeId = "node-" + node.id;
      // 避免重复makeSource/makeTarget
      if (node.type === "start") {
        if (!this._madeSource || !this._madeSource[nodeId]) {
          this.jsPlumbInstance.makeSource("anchor-out-" + node.id, {
            parent: nodeId,
            anchor: ["Right", { shape: "Rectangle", width: 60, height: 60 }],
            connector: ["Flowchart", { cornerRadius: 5 }],
            connectorStyle: { stroke: "#409eff", strokeWidth: 2 },
            endpoint: ["Dot", { radius: 8 }],
            endpointStyle: { fill: "#409eff" },
            maxConnections: -1,
            allowLoopback: false,
            allowNodeAfterConnection: true,
          });
          this._madeSource = this._madeSource || {};
          this._madeSource[nodeId] = true;
        }
        this.jsPlumbInstance.draggable(nodeId, { containment: "parent" });
      } else if (node.type === "end") {
        if (!this._madeTarget || !this._madeTarget[nodeId]) {
          this.jsPlumbInstance.makeTarget("anchor-in-" + node.id, {
            parent: nodeId,
            anchor: ["Left", { shape: "Rectangle", width: 60, height: 60 }],
            allowLoopback: false,
            dropOptions: { hoverClass: "dragHover" },
            endpoint: ["Dot", { radius: 8 }],
            endpointStyle: { fill: "#409eff" },
            maxConnections: -1,
            allowNodeAfterConnection: true,
          });
          this._madeTarget = this._madeTarget || {};
          this._madeTarget[nodeId] = true;
        }
        this.jsPlumbInstance.draggable(nodeId, { containment: "parent" });
      } else {
        if (!this._madeSource || !this._madeSource[nodeId]) {
          this.jsPlumbInstance.makeSource("anchor-out-" + node.id, {
            parent: nodeId,
            anchor: ["Right", { shape: "Rectangle", width: 60, height: 60 }],
            connector: ["Flowchart", { cornerRadius: 5 }],
            connectorStyle: { stroke: "#409eff", strokeWidth: 2 },
            endpoint: ["Dot", { radius: 8 }],
            endpointStyle: { fill: "#409eff" },
            maxConnections: -1,
            allowLoopback: false,
            allowNodeAfterConnection: true,
          });
          this._madeSource = this._madeSource || {};
          this._madeSource[nodeId] = true;
        }
        if (!this._madeTarget || !this._madeTarget[nodeId]) {
          this.jsPlumbInstance.makeTarget("anchor-in-" + node.id, {
            parent: nodeId,
            anchor: ["Left", { shape: "Rectangle", width: 60, height: 60 }],
            allowLoopback: false,
            dropOptions: { hoverClass: "dragHover" },
            endpoint: ["Dot", { radius: 8 }],
            endpointStyle: { fill: "#409eff" },
            maxConnections: -1,
            allowNodeAfterConnection: true,
          });
          this._madeTarget = this._madeTarget || {};
          this._madeTarget[nodeId] = true;
        }
        this.jsPlumbInstance.draggable(nodeId, { containment: "parent" });
      }
    },
    renderAllConnections() {
      if (!this.jsPlumbInstance) return;
      this.jsPlumbInstance.deleteEveryConnection();
      if (this.links && this.links.length > 0) {
        this.links.forEach((link, idx) => {
          if (
            typeof link.from !== "number" ||
            typeof link.to !== "number" ||
            !this.nodes[link.from] ||
            !this.nodes[link.to]
          ) {
            console.warn("跳过无效连线", link, idx);
            return;
          }
          const fromId = "anchor-out-" + this.nodes[link.from].id;
          const toId = "anchor-in-" + this.nodes[link.to].id;
          if (
            !document.getElementById(fromId) ||
            !document.getElementById(toId)
          ) {
            console.warn("跳过未渲染的连线DOM", fromId, toId);
            return;
          }
          const conn = this.jsPlumbInstance.connect({
            source: fromId,
            target: toId,
            overlays: [
              [
                "Arrow",
                {
                  width: 12,
                  length: 12,
                  location: 1,
                  foldback: 0.7,
                  paintStyle: { fill: "#409eff" },
                },
              ],
            ],
            paintStyle: { stroke: "#409eff", strokeWidth: 2 },
            hoverPaintStyle: { stroke: "#67c23a", strokeWidth: 3 },
            cssClass: "workflow-link",
            endpoint: ["Dot", { radius: 4 }],
            endpointStyle: { fill: "#409eff" },
          });
          if (!conn._hasClickBind) {
            conn.bind("click", (info, e) => {
              try {
                let connection = null;
                if (info && info.endpoints) {
                  connection = info;
                } else if (e && e.target && e.target._jsPlumb) {
                  connection = e.target._jsPlumb;
                }
                if (e) e.stopPropagation();
                if (connection) {
                  this.showLinkCard(idx, connection);
                } else {
                  this.$message.error("未能获取到连线对象");
                }
              } catch (err) {
                console.error("连线点击异常", err, info, e);
                this.$message.error("连线点击异常");
              }
            });
            conn._hasClickBind = true;
          }
          if (conn.canvas) {
            conn.canvas.addEventListener("mouseenter", () => {
              conn.setPaintStyle({ stroke: "#67c23a", strokeWidth: 3 });
            });
            conn.canvas.addEventListener("mouseleave", () => {
              conn.setPaintStyle({ stroke: "#409eff", strokeWidth: 2 });
            });
          }
        });
      }
    },
    // 拖拽释放
    onDrop(e) {
      if (!this.dragNodeType) return;
      // 获取画布相对坐标
      const canvasRect = e.currentTarget.getBoundingClientRect();
      const x = (e.clientX - canvasRect.left) / this.canvasScale;
      const y = (e.clientY - canvasRect.top) / this.canvasScale;
      // 新增节点时补全所有属性，防止设置卡片异常
      const nodeNames = {
        start: "开始节点",
        loop: "循环节点",
        knowledge: "知识库节点",
        code: "动态代码节点",
        end: "结束节点",
      };
      const nodeDescs = {
        start: "开始定义输入参数",
        loop: "循环执行指定逻辑",
        knowledge: "从知识库中检索相关信息",
        code: "执行自定义JavaScript代码",
        end: "工作流执行结束，返回结果",
      };
      this.nodes.push({
        id: Date.now() + Math.random(),
        type: this.dragNodeType,
        x,
        y,
        name: nodeNames[this.dragNodeType] || "新节点",
        desc: nodeDescs[this.dragNodeType] || "",
        async: false,
        loop: false,
        config: this.getDefaultConfig(this.dragNodeType),
      });
      this.dragNodeType = null;
      this.$nextTick(() => {
        this.renderAllConnections(); // 新增：节点新增后重新渲染连线
      });
    },
    // 拖拽节点移动
    onNodeMouseDown(e, i) {
      this.draggingNodeIndex = i;
      this.dragOffset = {
        x: e.clientX - this.nodes[i].x * this.canvasScale,
        y: e.clientY - this.nodes[i].y * this.canvasScale,
      };
      this.isDraggingNode = true;
      window.removeEventListener("click", this._handleClickOutsideSetting);
      document.addEventListener("mousemove", this.onNodeMouseMove);
      document.addEventListener("mouseup", this.onNodeMouseUp);
    },
    onNodeMouseMove(e) {
      if (this.draggingNodeIndex === null) return;
      const x = (e.clientX - this.dragOffset.x) / this.canvasScale;
      const y = (e.clientY - this.dragOffset.y) / this.canvasScale;
      this.$set(this.nodes, this.draggingNodeIndex, {
        ...this.nodes[this.draggingNodeIndex],
        x,
        y,
      });
    },
    onNodeMouseUp() {
      this.draggingNodeIndex = null;
      this.isDraggingNode = false;
      this.ignoreNextClick = true; // 拖拽后忽略一次 click
      window.addEventListener("click", this._handleClickOutsideSetting);
      document.removeEventListener("mousemove", this.onNodeMouseMove);
      document.removeEventListener("mouseup", this.onNodeMouseUp);
      // 拖动结束后刷新连线
      if (this.jsPlumbInstance) {
        this.jsPlumbInstance.repaintEverything();
      }
    },
    // 节点操作按钮
    copyNode(i) {
      const node = this.nodes[i];
      this.nodes.push({
        ...JSON.parse(JSON.stringify(node)),
        id: Date.now() + Math.random(),
        x: node.x + 30,
        y: node.y + 30,
      });
      this.$nextTick(() => {
        this.renderAllConnections(); // 新增：节点复制后重新渲染连线
      });
    },
    deleteNode(i) {
      // 删除节点
      this.nodes.splice(i, 1);
      // 删除与该节点相关的所有连线
      this.links = this.links.filter(
        (link) => link.from !== i && link.to !== i
      );
      // 修正剩余连线的 from/to 下标（因为节点数组下标变了）
      this.links.forEach((link) => {
        if (link.from > i) link.from--;
        if (link.to > i) link.to--;
      });
      if (this.settingNodeIndex === i) {
        this.settingNodeIndex = null;
      } else if (this.settingNodeIndex > i) {
        this.settingNodeIndex--;
      }
      this.$nextTick(() => {
        this.renderAllConnections(); // 新增：节点删除后重新渲染连线
      });
    },
    settingNode(i) {
      // 切换显示/隐藏
      this.settingNodeIndex = this.settingNodeIndex === i ? null : i;
      this.ignoreNextClick = false; // 展开卡片时重置，保证单击一次即可关闭
    },
    handleSettingInput(key, val) {
      if (this.settingNodeIndex !== null) {
        this.$set(this.nodes, this.settingNodeIndex, {
          ...this.nodes[this.settingNodeIndex],
          [key]: val,
        });
      }
    },
    handleClickOutsideSetting(e) {
      if (this.ignoreNextClick) {
        this.ignoreNextClick = false;
        return;
      }
      // 拖拽时不关闭设置卡片
      if (this.isDraggingNode) return;
      // 判断点击是否在设置卡片内
      if (
        !e.target.closest(".node-setting-card") &&
        !e.target.closest(".el-icon-setting")
      ) {
        this.settingNodeIndex = null;
      }
    },
    getDefaultConfig(type) {
      if (this.NODE_CONFIG[type]) {
        // 可根据不同类型返回不同默认配置
        switch (type) {
          case "knowledge":
            return {
              knowledgeBase: "",
              searchType: "semantic",
              topK: 5,
              similarityThreshold: 0.7,
              query: "",
              saveToVariable: false,
              outputVariable: "",
            };
          case "ai":
            return { prompt: "", model: "gpt-3.5-turbo", temperature: 0.7 };
          case "search":
            return { engine: "bing", query: "" };
          case "http":
            return { url: "", method: "GET", headers: {}, body: "" };
          case "code":
            return { code: "", inputVar: "", outputVar: "" };
          case "template":
            return { template: "", variables: [] };
          case "extract":
            return { fileType: "", extractType: "text" };
          case "generate":
            return { fileName: "", content: "" };
          case "sql":
            return { sql: "", db: "" };
          case "plugin":
            return { pluginId: "", params: {} };
          default:
            return {};
        }
      }
      return {};
    },
    handleDeleteAllNodes() {
      this.$confirm("确定要删除所有节点吗？此操作不可恢复！", "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      }).then(() => {
        this.nodes = [];
        this.links = [];
        this.$message.success("所有节点已删除");
      });
    },
  },
};
</script>

<style scoped>
.designer-container {
  display: flex;
  height: 100vh;
  background: #fafafa;
}
.designer-sidebar {
  width: 180px !important;
  min-width: 180px !important;
  max-width: 180px !important;
  background: #fff;
  border-right: 1px solid #eee;
  padding: 16px 0 0 0;
  display: flex;
  flex-direction: column;
  position: relative;
  transition: width 0.2s;
  height: 100vh;
  min-height: 0;
}
.designer-sidebar.sidebar-collapsed {
  width: 30px !important;
  min-width: 30px !important;
  max-width: 30px !important;
}
.sidebar-expand-btn {
  position: absolute;
  right: -12px;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 20px;
  color: #409eff;
  z-index: 10;
  box-shadow: none;
  border-radius: 50%;
  transition: background 0.2s;
}
.sidebar-expand-btn:hover {
  background: #f0f0f0;
}
.designer-tabs {
  margin-bottom: 0;
}
.sidebar-title {
  font-weight: bold;
  margin-bottom: 12px;
  padding-left: 24px;
  font-size: 16px;
}
.sidebar-list {
  flex: 1 1 auto;
  overflow-y: visible;
  min-height: 0;
}
.sidebar-item {
  padding: 10px 24px;
  cursor: pointer;
  font-size: 15px;
  color: #333;
  border-radius: 6px;
  margin-bottom: 2px;
  transition: background 0.2s;
  display: flex;
  align-items: center;
}
.sidebar-item:hover {
  background: #f5f7fa;
}
.sidebar-icon {
  font-size: 20px;
  margin-right: 10px;
}
.designer-main {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.designer-header {
  height: 56px;
  display: flex;
  align-items: center;
  padding: 0 32px;
  border-bottom: 1px solid #eee;
  background: #fff;
  width: 100%;
  margin: 0;
  box-sizing: border-box;
}
.designer-title {
  font-size: 20px;
  font-weight: 600;
}
.designer-actions {
  display: flex;
  align-items: center;
}
.designer-canvas {
  width: 100%;
  height: 100%;
  flex: 1;
  position: relative;
  background-color: #fafbfc;
  background-image: radial-gradient(circle, #d8dbe0 1.2px, transparent 1.2px);
  background-size: 24px 24px;
  overflow: auto;
  /* 新增：缩放动画 */
  transition: transform 0.2s;
}
/* 新增：缩放按钮区域 */
.sidebar-zoom-btns {
  position: absolute;
  right: 32px;
  top: 80px;
  left: auto;
  bottom: auto;
  transform: none;
  display: flex;
  flex-direction: column; /* 竖排显示 */
  gap: 8px;
  z-index: 20;
}
.zoom-btn {
  width: 32px;
  height: 32px;
  border: none;
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
  font-size: 20px;
  color: #409eff;
  cursor: pointer;
  transition: background 0.2s;
}
.zoom-btn:hover {
  background: #f0f0f0;
}
.canvas-watermark {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 80px;
  color: #e5e7eb;
  opacity: 0.4;
  pointer-events: none;
  user-select: none;
  font-weight: bold;
  z-index: 0;
}
.canvas-node {
  position: absolute;
  min-width: 120px;
  min-height: 48px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  font-weight: 500;
  z-index: 1;
  user-select: none;
}
.node-actions {
  position: absolute;
  right: 4px;
  top: 4px;
  display: flex;
  gap: 2px;
  z-index: 2;
}
/* 删除开始节点、动态代码、结束节点样式 */
/* 让抽屉内容靠近顶部 */
.drawer-top .el-drawer__wrapper {
  align-items: flex-start !important;
}
.drawer-top .el-drawer__container {
  margin-top: 0 !important;
}
.node-setting-card {
  position: absolute;
  left: 100%;
  top: 0;
  margin-left: 16px;
  z-index: 10;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  border-radius: 10px;
  padding: 20px;
  min-width: 300px;
}
.anchor-point {
  width: 12px;
  height: 12px;
  background: #409eff;
  border-radius: 50%;
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(50%, -50%);
  cursor: crosshair;
  z-index: 2;
  border: 2px solid #fff;
  box-shadow: 0 0 2px #409eff;
  pointer-events: auto;
}
.link-card {
  position: fixed;
  min-width: 180px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.12);
  padding: 12px 16px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: stretch;
}
.dragHover {
  box-shadow: 0 0 0 8px rgba(64, 158, 255, 0.15);
  border: 2px solid #67c23a !important;
}
.designer-sidebar.sidebar-collapsed .sidebar-item span {
  display: none;
}
.designer-sidebar.sidebar-collapsed .sidebar-item {
  justify-content: center;
  padding: 10px 0;
}
.designer-sidebar.sidebar-collapsed .sidebar-title {
  display: none;
}
.designer-sidebar.sidebar-collapsed .designer-tabs {
  display: none;
}
</style> 